/*
 * CProcGeneric.java
 * 
 * Copyright (c) 2009 Giles Millward (Manigra). All rights reserved.
 * 
 * This file is part of JRogueDPS.
 * 
 * JRogueDPS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * JRogueDPS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with JRogueDPS.  If not, see <http://www.gnu.org/licenses/>.
 */

package dpssystem;

import dpsstats.CProcStats;
import geardatabase.CProcItem;
import java.util.ArrayList;

/**
 *
 * @author Giles Millward (Manigra)
 */
public class CProcGeneric extends CProc {

    double  m_ppm; // Procs per minute
    double  m_procChance; // plain old proc chance
    double  m_procLength; // seconds
    int     m_internalCDLength; // internal cooldown length (0 = no cooldown)
    public  boolean m_isProcing = false; // flag to say if proc is in progress
    double  m_offCooldown = -1.0; // time into simulation where proc is no longer on internal CD
    double  m_endOfProc = -1.0;

    // Attack types
    boolean m_whiteAttack;
    boolean m_specialAttack;
    boolean m_meleeFinisher;
    boolean m_spellFinisher;
    boolean m_deadlyPoison;
    boolean m_instantPoison;
    boolean m_woundPoison;
    boolean m_anestheticPoison;
    boolean m_rupture;
    boolean m_onHit;
    boolean m_onCrit;

    boolean m_stackable; // Stacks with another proc of the same id
    boolean m_onUse;     // Is an on use trinket? (Not really a proc as such but it needs to be triggered)
    int     m_maxStackSize;    // Maximum number of stacks it can grow to
    int     m_onUseInitialDelay; // Initial delay before use of proc

    int     m_stackCount = 0;
    // Weapon this is applied to if it is a weapon enchant
    CWeapon m_weapon;

    CProcGeneric(CProcItem p_procItem, CProcStats p_procStats)
    {
        super(p_procItem.m_name, p_procStats);
        initProc(p_procItem, null);
    }

    CProcGeneric(CProcItem p_procItem, CProcStats p_procStats, CWeapon p_weapon)
    {
        super(p_procItem.m_name, p_procStats);
        initProc(p_procItem, p_weapon);
    }

    private void initProc(CProcItem p_procItem, CWeapon p_weapon)
    {
        m_stats = p_procItem.m_stats;
        m_ppm = p_procItem.m_ppm;
        m_procChance = p_procItem.m_procChance;
        m_procLength = p_procItem.m_procLength;
        m_internalCDLength = p_procItem.m_internalCDLength;
        m_whiteAttack = p_procItem.m_whiteAttack;
        m_specialAttack = p_procItem.m_specialAttack;
        m_meleeFinisher = p_procItem.m_meleeFinisher;
        m_spellFinisher = p_procItem.m_spellFinisher;
        m_deadlyPoison = p_procItem.m_deadlyPoison;
        m_instantPoison = p_procItem.m_instantPoison;
        m_woundPoison = p_procItem.m_woundPoison;
        m_anestheticPoison = p_procItem.m_anestheticPoison;
        m_rupture = p_procItem.m_rupture;
        m_onHit = p_procItem.m_onHit;
        m_onCrit = p_procItem.m_onCrit;
        m_weapon = p_weapon;
        m_stackable = p_procItem.m_stackable;
        m_onUse = p_procItem.m_onUse;
        m_maxStackSize = p_procItem.m_maxStackSize;
        m_onUseInitialDelay = p_procItem.m_onUseInitialDelay;
    }

    public void executeProc(CSim p_sim, AttackType p_attackType, boolean p_crit, CWeapon p_weapon)
    {
        if (m_onUse)
        {
            // On Use Trinkets go through a different process
            return;
        }

        if (this.m_internalCDLength != 0)
        {
            if (this.m_offCooldown > p_sim.m_currentRunTime)
            {
                // Still on internal CD
                return;
            }
        }

        // p_attackType = Attack type source
        if (p_attackType == AttackType.MeleeWhite && m_whiteAttack)
        {
            if (testHitType(p_crit))
            {
                // Calculate swing time adding on haste effects as procs are not
                // affected by haste (this would make haste too powerful)
                if (m_ppm > 0)
                {
                    double l_procChance = (p_weapon.m_weaponSpeed * p_sim.m_haste.getHaste()) /
                                            (60.0 / m_ppm);
                    if (p_sim.m_rng.nextDouble() <= l_procChance)
                    {
                        procEffect(p_sim, p_weapon);
                    }
                }
                else if (m_procChance > 0.0)
                {
                    if (p_sim.m_rng.nextDouble() <= m_procChance)
                    {
                        procEffect(p_sim, p_weapon);
                    }
                }
                else
                {
                    // Not a ppm effect
                    procEffect(p_sim, p_weapon);
                }
            }
        } else if ((p_attackType == AttackType.MeleeSpecial && m_specialAttack) ||
                    (p_attackType == AttackType.MeleeFinisher && m_meleeFinisher))
        {
            if (testHitType(p_crit))
            {
                // Specials are instant attacks so no haste modifier needed
                if (m_ppm > 0.0)
                {
                    double l_procChance = m_ppm / (60.0 / p_weapon.m_weaponSpeed);
                    if (p_sim.m_rng.nextDouble() <= l_procChance)
                    {
                        procEffect(p_sim, p_weapon);
                    }
                }
                else if (m_procChance > 0.0)
                {
                    if (p_sim.m_rng.nextDouble() <= m_procChance)
                    {
                        procEffect(p_sim, p_weapon);
                    }
                }
                else
                {
                    // Not a ppm effect
                    procEffect(p_sim, p_weapon);
                }
            }
        } else if (p_attackType == AttackType.SpellFinisher && m_spellFinisher)
        {
            if (testHitType(p_crit))
            {
            }
        } else if (p_attackType == AttackType.DeadlyPoison && m_deadlyPoison)
        {
            if (testHitType(p_crit))
            {
            }
        } else if (p_attackType == AttackType.InstantPoison && m_instantPoison)
        {
            if (testHitType(p_crit))
            {
            }
        } else if (p_attackType == AttackType.WoundPoison && m_woundPoison)
        {
            if (testHitType(p_crit))
            {
            }
        } else if (p_attackType == AttackType.Anesthetic_Poison && m_anestheticPoison)
        {
            if (testHitType(p_crit))
            {
            }
        } else if (p_attackType == AttackType.Rupture && m_rupture)
        {
            if (testHitType(p_crit))
            {
            }
        }
    }

    private void procEffect(CSim p_sim, CWeapon p_weapon)
    {
        // Proc'd
        if (!m_stackable &&
             p_weapon != null &&
             p_weapon.m_otherHandWeapon.m_enchantId == p_weapon.m_enchantId)
        {
            // Same enchant is on the other weapon and this is not an enchant
            // that will stack (executioner for example)
            if (((CProcGeneric) p_weapon.m_otherHandWeapon.m_enchantProc).m_isProcing)
            {
                // Do not let this proc happen while the other enchant
                // is in full flow
                return;
            }
        }

        this.m_procStats.addProc();

        if (this.m_isProcing)
        {
            // Reset the end of proc timeout
            this.m_endOfProc = p_sim.m_currentRunTime + this.m_procLength;
            if (this.m_maxStackSize != 0)
            {
                // This is a stacker type trinket
                if (this.m_maxStackSize > this.m_stackCount)
                {
                    // Adjust the stats
                    p_sim.m_baseStats.adjust(m_stats, 1);
                    this.m_stackCount++;
                }
            }
        }
        else
        {
            // New proc
            this.m_endOfProc = p_sim.m_currentRunTime + this.m_procLength;
            this.m_isProcing = true;
            p_sim.m_baseStats.adjust(m_stats, 1);
            if (this.m_maxStackSize != 0)
            {
                // This is a stacker type trinket
                if (this.m_maxStackSize > this.m_stackCount)
                {
                    // Adjust the stats                    
                    p_sim.m_baseStats.adjust(m_stats, 1);
                    this.m_stackCount++;
                }
            }


            p_sim.m_eventList.add(new EndProcEvent(this.m_endOfProc));
            this.m_procStats.addUptime(Math.min((p_sim.m_maxRunTime - p_sim.m_currentRunTime), m_procLength));

            if (this.m_internalCDLength != 0)
            {
                this.m_offCooldown = p_sim.m_currentRunTime + this.m_internalCDLength;
            }
        }
    }

    private boolean testHitType(boolean p_crit)
    {
        if (p_crit)
        {
            // Attack was a crit
            if (m_onCrit)
            {
                return true;
            }
        } else
        {
            // Attack was just a normal
            if (m_onHit)
            {
                return true;
            }
        }

        return false;
    }

    public void initOnUseProc(CSim p_sim)
    {
        // Initial event creation for the use of the trinket.
        if (this.m_onUse)
        {
            p_sim.m_eventList.add(new OnUseEvent(p_sim.m_currentRunTime + this.m_onUseInitialDelay));
        }
    }

    public class EndProcEvent extends CEvent
    {
        EndProcEvent(double p_eventTime)
        {
            super(p_eventTime);
        }

        public ArrayList<CEvent>runEvent(CSim p_sim)
        {
            // End of proc
            if (m_endOfProc < p_sim.m_currentRunTime + 0.0001)
            {
                m_isProcing = false;
                if (m_maxStackSize != 0)
                {
                    // This is a stacker type trinket so remove stacked
                    // stats
                    for (int i = 0; i < m_stackCount; i++)
                    {
                        // Adjust the stats
                        p_sim.m_baseStats.adjust(m_stats, -1);
                    }

                    m_stackCount = 0;
                }
                else
                {
                    // Remove stat boost
                    p_sim.m_baseStats.adjust(m_stats, -1);
                }
            }
            else
            {
                p_sim.m_eventList.add(new EndProcEvent(m_endOfProc));
                m_procStats.addUptime(Math.min((p_sim.m_maxRunTime - m_endOfProc), (m_endOfProc - p_sim.m_currentRunTime)));
            }

            return null;
        }
    };

    // A trinket that is 'On Use' triggers this
    public class OnUseEvent extends CEvent
    {
        OnUseEvent(double p_eventTime)
        {
            super(p_eventTime);
        }

        public ArrayList<CEvent>runEvent(CSim p_sim)
        {
            // Start up the trinket effect
            m_procStats.addProc();
            m_procStats.addUptime(Math.min((p_sim.m_maxRunTime - (p_sim.m_currentRunTime + m_procLength)), m_procLength));
            p_sim.m_baseStats.adjust(m_stats, 1);
            ArrayList<CEvent> l_events = new ArrayList<CEvent>();
            // Queue up the end of event
            l_events.add(new EndProcEvent(p_sim.m_currentRunTime + m_procLength));
            // Queue up the next time the trinket is available for use
            l_events.add(new OnUseEvent(p_sim.m_currentRunTime + m_internalCDLength));
            return l_events;
        }
    };
}
